package com.pangus.ims.mes.controller;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import com.pangus.error.exception.BaseException;
import com.pangus.error.exception.LogicWrongStateException;
import com.pangus.ims.report.ureport.Utils;
import com.pangus.ims.report.ureport.build.cell.CellBuilder;
import com.pangus.ims.report.ureport.build.cell.NoneExpandBuilder;
import com.pangus.ims.report.ureport.build.cell.down.DownExpandBuilder;
import com.pangus.ims.report.ureport.build.cell.right.RightExpandBuilder;
import com.pangus.ims.report.ureport.build.paging.BasePagination;
import com.pangus.ims.report.ureport.build.paging.Page;
import com.pangus.ims.report.ureport.build.paging.PagingBuilder;
import com.pangus.ims.report.ureport.definition.Band;
import com.pangus.ims.report.ureport.definition.Expand;
import com.pangus.ims.report.ureport.definition.Orientation;
import com.pangus.ims.report.ureport.definition.PagingMode;
import com.pangus.ims.report.ureport.definition.Paper;
import com.pangus.ims.report.ureport.definition.ReportDefinition;
import com.pangus.ims.report.ureport.definition.datasource.BuildinDatasource;
import com.pangus.ims.report.ureport.definition.datasource.BuildinDatasourceDefinition;
import com.pangus.ims.report.ureport.definition.datasource.DataCellDatasourceDefinition;
import com.pangus.ims.report.ureport.definition.datasource.DatasourceDefinition;
import com.pangus.ims.report.ureport.definition.datasource.DatasourceProvider;
import com.pangus.ims.report.ureport.definition.datasource.JdbcDatasourceDefinition;
import com.pangus.ims.report.ureport.definition.datasource.RestFulDatasourceDefinition;
import com.pangus.ims.report.ureport.definition.datasource.SpringBeanDatasourceDefinition;
import com.pangus.ims.report.ureport.exception.ReportComputeException;
import com.pangus.ims.report.ureport.exception.ReportException;
import com.pangus.ims.report.ureport.model.Cell;
import com.pangus.ims.report.ureport.model.Column;
import com.pangus.ims.report.ureport.model.Report;
import com.pangus.ims.report.ureport.model.Row;

public class ReportBuilder extends BasePagination implements ApplicationContextAware {
	private static final ResourceBundle UREPORT = ResourceBundle.getBundle("ureport");
	public static final String BEAN_ID = "ureport.reportBuilder";
	private ApplicationContext applicationContext;
	private Map<String, DatasourceProvider> datasourceProviderMap = new HashMap<String, DatasourceProvider>();
	private Map<Expand, CellBuilder> cellBuildersMap = new HashMap<Expand, CellBuilder>();
	private NoneExpandBuilder noneExpandBuilder = new NoneExpandBuilder();
	private HideRowColumnBuilder hideRowColumnBuilder;

	public ReportBuilder() {
		cellBuildersMap.put(Expand.Right, new RightExpandBuilder());
		cellBuildersMap.put(Expand.Down, new DownExpandBuilder());
		cellBuildersMap.put(Expand.None, noneExpandBuilder);
	}

	public Report buildReport(ReportDefinition reportDefinition, Map<String, Object> parameters) {
		Report report = reportDefinition.newReport();
		Map<String, Dataset> datasetMap = buildDatasets(reportDefinition, parameters, applicationContext);
		Context context = new Context(this, report, datasetMap, applicationContext, parameters, hideRowColumnBuilder);
		long start = System.currentTimeMillis();
		/**
		 * 修改成超时阻止
		 * updated by nyl 2019_12_31
		 */
		ExecutorService executor = Executors.newSingleThreadExecutor();
		FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
			@Override
			public String call() throws Exception {
				// TODO Auto-generated method stub
				List<Cell> cells = new ArrayList<Cell>();
				cells.add(report.getRootCell());
				do {
					buildCell(context, cells);
					cells = context.nextUnprocessedCells();
				} while (cells != null);

				doFillBlankRows(report, context);
				recomputeCells(report, context);
				return null;
			}

		});
		executor.execute(futureTask);
		try {
			Object object = UREPORT.getObject("ureport.outtime.seconds");
			Integer OUTTIME_SECONDS = Integer.parseInt(object + "");
			futureTask.get(OUTTIME_SECONDS, TimeUnit.MILLISECONDS);
		} catch (Exception e) {
			futureTask.cancel(true);//关闭任务
			List<Runnable> shutdownNow = executor.shutdownNow();
			boolean shutdown = executor.isShutdown();
			long end = System.currentTimeMillis();
			String msg = "~~~ Report TimeoutException:" + (end - start) + "ms";
			Utils.logToConsole(msg);
			Utils.logToConsole("executor.isShutdown():"+shutdown);
			BaseException BaseException = new LogicWrongStateException();
			BaseException.setMessage(e.toString());
			throw BaseException;
		}
		long end = System.currentTimeMillis();
		String msg = "~~~ Report compute completed:" + (end - start) + "ms";
		futureTask.cancel(true);//关闭任务
		List<Runnable> shutdownNow = executor.shutdownNow();
		//boolean shutdown = executor.isShutdown();
		futureTask = null;
		executor = null;
		Utils.logToConsole(msg);
		return report;
	}

	public void buildCell(Context context, List<Cell> cells) {
		if (cells == null) {
			cells = context.nextUnprocessedCells();
		}
		if (cells == null) {
			return;
		}
		for (Cell cell : cells) {
			List<BindData> dataList = context.buildCellData(cell);
			cell.setProcessed(true);
			int size = dataList.size();
			Cell lastCell = cell;
			int rowNumber = cell.getRow().getRowNumber();
			int columnNumber = cell.getColumn().getColumnNumber();
			//System.out.println(rowNumber+"::::"+columnNumber);
			if (size == 1) {
				lastCell = noneExpandBuilder.buildCell(dataList, cell, context);
			} else if (size > 1) {
				CellBuilder cellBuilder = cellBuildersMap.get(cell.getExpand());
				lastCell = cellBuilder.buildCell(dataList, cell, context);
			}
			if (lastCell.isFillBlankRows() && lastCell.getMultiple() > 0) {
				int result = size % lastCell.getMultiple();
				if (result > 0) {
					int value = lastCell.getMultiple() - result;
					context.addFillBlankRow(lastCell.getRow(), value);
				}
			}
		}
	}

	private Map<String, Dataset> buildDatasets(ReportDefinition reportDefinition, Map<String, Object> parameters,
			ApplicationContext applicationContext) {
		Map<String, Dataset> datasetMap = new HashMap<String, Dataset>();
		List<DatasourceDefinition> datasources = reportDefinition.getDatasources();
		if (datasources == null) {
			return datasetMap;
		}
		for (DatasourceDefinition dsDef : datasources) {
			if (dsDef instanceof JdbcDatasourceDefinition) {
				String dsName = dsDef.getName();
				Connection conn = null;
				try {
					if (datasourceProviderMap.containsKey(dsName)) {
						conn = datasourceProviderMap.get(dsName).getConnection();
					}
					JdbcDatasourceDefinition ds = (JdbcDatasourceDefinition) dsDef;
					List<Dataset> ls = ds.buildDatasets(conn, parameters);
					if (ls != null) {
						for (Dataset dataset : ls) {
							datasetMap.put(dataset.getName(), dataset);
						}
					}
				} finally {
					if (conn != null) {
						try {
							conn.close();
						} catch (SQLException e) {
						}
					}
				}
			} else if (dsDef instanceof SpringBeanDatasourceDefinition) {
				SpringBeanDatasourceDefinition ds = (SpringBeanDatasourceDefinition) dsDef;
				List<Dataset> ls = ds.getDatasets(applicationContext, parameters);
				if (ls != null) {
					for (Dataset dataset : ls) {
						datasetMap.put(dataset.getName(), dataset);
					}
				}
			} else if (dsDef instanceof BuildinDatasourceDefinition ||(dsDef instanceof DataCellDatasourceDefinition&&parameters!=null&&"buildin".equals(parameters.get("sign")))) {
				String dsName = dsDef.getName();
				Connection conn = null;
				try {
					if (datasourceProviderMap.containsKey(dsName)) {
						conn = datasourceProviderMap.get(dsName).getConnection();
					}
					for (BuildinDatasource datasource : Utils.getBuildinDatasources()) {
						if (datasource.name().equals(dsName)) {
							conn = datasource.getConnection();
							break;
						}
					}
					if (conn == null) {
						throw new ReportComputeException("Buildin datasource [" + dsName + "] not exist.");
					}
					BuildinDatasourceDefinition ds = (BuildinDatasourceDefinition) dsDef;
					List<Dataset> ls = ds.buildDatasets(conn, parameters);
					if (ls != null) {
						for (Dataset dataset : ls) {
							datasetMap.put(dataset.getName(), dataset);
						}
					}
				} finally {
					if (conn != null) {
						try {
							conn.close();
						} catch (SQLException e) {
						}
					}
				}
			} else if (dsDef instanceof RestFulDatasourceDefinition) {
				String dsName = dsDef.getName();
				RestFulDatasourceDefinition restFulDatasourceDefinition = (RestFulDatasourceDefinition) dsDef;
				try {
					List<Dataset> dlist = restFulDatasourceDefinition.buildDatasets(dsName, parameters);
					if (dlist != null) {
						for (Dataset dataset : dlist) {
							datasetMap.put(dataset.getName(), dataset);
						}
					}
				} catch (Exception e) {
					throw new ReportComputeException("Buildin datasource [" + dsName + "] not exist.");
				}
			} else if (dsDef instanceof DataCellDatasourceDefinition) {
				String dsName = dsDef.getName();
				DataCellDatasourceDefinition dataCellDatasourceDefinition = (DataCellDatasourceDefinition) dsDef;
				try {
					List<Dataset> dlist = dataCellDatasourceDefinition.buildDatasets(dsName, parameters);
					if (dlist != null) {
						for (Dataset dataset : dlist) {
							datasetMap.put(dataset.getName(), dataset);
						}
					}
				} catch (Exception e) {
					e.printStackTrace();
					throw new ReportComputeException("Buildin datasource [" + dsName + "] not exist.");
				}
			}
		}
		return datasetMap;
	}

	private void doFillBlankRows(Report report, Context context) {
		Map<Row, Integer> map = context.getFillBlankRowsMap();
		List<Row> newRowList = new ArrayList<Row>();
		for (Row row : map.keySet()) {
			int size = map.get(row);
			Row lastRow = findLastRow(row, report);
			for (int i = 0; i < size; i++) {
				Row newRow = buildNewRow(lastRow, report);
				newRowList.add(newRow);
			}
			int rowNumber = lastRow.getRowNumber();
			if (newRowList.size() > 0) {
				report.insertRows(rowNumber + 1, newRowList);
				newRowList.clear();
			}
		}
	}

	private Row buildNewRow(Row row, Report report) {
		Row newRow = row.newRow();
		newRow.setBand(null);
		List<Row> rows = report.getRows();
		List<Column> columns = report.getColumns();
		int start = -1, colSize = columns.size();
		Map<Row, Map<Column, Cell>> rowMap = report.getRowColCellMap();
		Map<Column, Cell> newCellMap = new HashMap<Column, Cell>();
		rowMap.put(newRow, newCellMap);

		Map<Column, Cell> colMap = rowMap.get(row);
		for (int index = 0; index < colSize; index++) {
			Column column = columns.get(index);
			Cell currentCell = colMap.get(column);
			if (currentCell == null) {
				if (start == -1) {
					start = row.getRowNumber() - 2;
				}
				for (int i = start; i > -1; i--) {
					Row currentRow = rows.get(i);
					Map<Column, Cell> prevColMap = rowMap.get(currentRow);
					if (prevColMap == null) {
						continue;
					}
					if (prevColMap.containsKey(column)) {
						currentCell = prevColMap.get(column);
						break;
					}
				}
			}
			if (currentCell == null) {
				throw new ReportException("Insert blank rows fail.");
			}
			int colSpan = currentCell.getColSpan();
			if (colSpan > 0) {
				colSpan--;
				index += colSpan;
			}
			int rowSpan = currentCell.getRowSpan();
			if (rowSpan > 1) {
				currentCell.setRowSpan(rowSpan + 1);
			} else {
				Cell newCell = newBlankCell(currentCell, column, report);
				newCell.setRow(newRow);
				newRow.getCells().add(newCell);
				newCellMap.put(newCell.getColumn(), newCell);
			}
		}
		return newRow;
	}

	private Row findLastRow(Row row, Report report) {
		List<Row> rows = report.getRows();
		List<Cell> cells = row.getCells();
		Row lastRow = row;
		int span = 0;
		for (Cell cell : cells) {
			int rowSpan = cell.getRowSpan();
			if (rowSpan < 2) {
				continue;
			}
			if (span == 0) {
				span = rowSpan;
			} else if (rowSpan > span) {
				span = rowSpan;
			}
		}
		if (span > 1) {
			int rowIndex = row.getRowNumber() - 1 + span - 1;
			lastRow = rows.get(rowIndex);
		}
		return lastRow;
	}

	private Cell newBlankCell(Cell cell, Column column, Report report) {
		Cell newCell = new Cell();
		newCell.setData("");
		newCell.setColSpan(cell.getColSpan());
		newCell.setConditionPropertyItems(cell.getConditionPropertyItems());
		report.addLazyCell(newCell);
		newCell.setCellStyle(cell.getCellStyle());
		newCell.setName(cell.getName());
		newCell.setColumn(column);
		column.getCells().add(newCell);
		Cell leftParent = cell.getLeftParentCell();
		if (leftParent != null) {
			newCell.setLeftParentCell(leftParent);
			leftParent.addRowChild(newCell);
		}
		Cell topParent = cell.getTopParentCell();
		if (topParent != null) {
			newCell.setTopParentCell(topParent);
			topParent.addColumnChild(newCell);
		}
		return newCell;
	}

	private void recomputeCells(Report report, Context context) {
		List<Cell> lazyCells = report.getLazyComputeCells();
		for (Cell cell : lazyCells) {
			cell.doCompute(context);
		}
		context.setDoPaging(true);
		List<Row> rows = report.getRows();
		int rowSize = rows.size();
		Paper paper = report.getPaper();
		PagingMode pagingMode = paper.getPagingMode();
		List<Row> headerRows = report.getHeaderRepeatRows();
		List<Row> footerRows = report.getFooterRepeatRows();
		List<Row> titleRows = report.getTitleRows();
		List<Row> summaryRows = report.getSummaryRows();
		List<Page> pages = new ArrayList<Page>();
		List<Row> pageRows = new ArrayList<Row>();
		int pageIndex = 1;
		List<Row> pageRepeatHeaders = new ArrayList<Row>();
		List<Row> pageRepeatFooters = new ArrayList<Row>();
		pageRepeatHeaders.addAll(headerRows);
		pageRepeatFooters.addAll(footerRows);
		if (pagingMode.equals(PagingMode.fitpage)) {
			int height = paper.getHeight() - paper.getBottomMargin() - paper.getTopMargin() - 5;
			if (paper.getOrientation().equals(Orientation.landscape)) {
				height = paper.getWidth() - paper.getBottomMargin() - paper.getTopMargin() - 5;
			}
			int repeatHeaderRowHeight = report.getRepeatHeaderRowHeight(),
					repeatFooterRowHeight = report.getRepeatFooterRowHeight();
			int titleRowHeight = report.getTitleRowsHeight();
			int rowHeight = titleRowHeight + repeatHeaderRowHeight + repeatFooterRowHeight;
			for (int i = 0; i < rowSize; i++) {
				Row row = rows.get(i);
				int rowRealHeight = row.getRealHeight();
				if (rowRealHeight == 0) {
					continue;
				}
				Band band = row.getBand();
				if (band != null) {
					String rowKey = row.getRowKey();
					int index = -1;
					if (band.equals(Band.headerrepeat)) {
						for (int j = 0; j < pageRepeatHeaders.size(); j++) {
							Row headerRow = pageRepeatHeaders.get(j);
							if (headerRow.getRowKey().equals(rowKey)) {
								index = j;
								break;
							}
						}
						pageRepeatHeaders.remove(index);
						pageRepeatHeaders.add(index, row);
					} else if (band.equals(Band.footerrepeat)) {
						for (int j = 0; j < pageRepeatFooters.size(); j++) {
							Row footerRow = pageRepeatFooters.get(j);
							if (footerRow.getRowKey().equals(rowKey)) {
								index = j;
								break;
							}
						}
						pageRepeatFooters.remove(index);
						pageRepeatFooters.add(index, row);
					}
					continue;
				}
				rowHeight += rowRealHeight + 1;
				pageRows.add(row);
				row.setPageIndex(pageIndex);
				boolean overflow = false;
				if ((i + 1) < rows.size()) {
					Row nextRow = rows.get(i + 1);
					if ((rowHeight + nextRow.getRealHeight()) > height) {
						overflow = true;
					}
				}
				if (!overflow && row.isPageBreak()) {
					overflow = true;
				}
				if (overflow) {
					Page newPage = buildPage(pageRows, pageRepeatHeaders, pageRepeatFooters, titleRows, pageIndex,
							report);
					pageIndex++;
					pages.add(newPage);
					rowHeight = repeatHeaderRowHeight + repeatFooterRowHeight;
					pageRows = new ArrayList<Row>();
				}
			}
			if (pageRows.size() > 0) {
				Page newPage = buildPage(pageRows, pageRepeatHeaders, pageRepeatFooters, titleRows, pageIndex, report);
				pages.add(newPage);
			}
			report.getContext().setTotalPages(pages.size());
			buildPageHeaderFooter(pages, report);
		} else {
			int fixRows = paper.getFixRows() - headerRows.size() - footerRows.size();
			if (fixRows < 0) {
				fixRows = 1;
			}
			for (int i = 0; i < rowSize; i++) {
				Row row = rows.get(i);
				int height = row.getRealHeight();
				if (height == 0) {
					continue;
				}
				Band band = row.getBand();
				if (band != null) {
					String rowKey = row.getRowKey();
					int index = -1;
					if (band.equals(Band.headerrepeat)) {
						for (int j = 0; j < pageRepeatHeaders.size(); j++) {
							Row headerRow = pageRepeatHeaders.get(j);
							if (headerRow.getRowKey().equals(rowKey)) {
								index = j;
								break;
							}
						}
						pageRepeatHeaders.remove(index);
						pageRepeatHeaders.add(index, row);
					} else if (band.equals(Band.footerrepeat)) {
						for (int j = 0; j < pageRepeatFooters.size(); j++) {
							Row footerRow = pageRepeatFooters.get(j);
							if (footerRow.getRowKey().equals(rowKey)) {
								index = j;
								break;
							}
						}
						pageRepeatFooters.remove(index);
						pageRepeatFooters.add(index, row);
					}
					continue;
				}
				row.setPageIndex(pageIndex);
				pageRows.add(row);
				if (pageRows.size() >= fixRows) {
					Page newPage = buildPage(pageRows, pageRepeatHeaders, pageRepeatFooters, titleRows, pageIndex,
							report);
					pageIndex++;
					pages.add(newPage);
					pageRows = new ArrayList<Row>();
				}
			}
			if (pageRows.size() > 0) {
				Page newPage = buildPage(pageRows, pageRepeatHeaders, pageRepeatFooters, titleRows, pageIndex, report);
				pages.add(newPage);
			}
			report.getContext().setTotalPages(pages.size());
			buildPageHeaderFooter(pages, report);
		}
		buildSummaryRows(summaryRows, pages);
		PagingBuilder.computeExistPageFunctionCells(report);
		report.setPages(pages);
	}

	public void setHideRowColumnBuilder(HideRowColumnBuilder hideRowColumnBuilder) {
		this.hideRowColumnBuilder = hideRowColumnBuilder;
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
		Collection<DatasourceProvider> datasourceProviders = applicationContext.getBeansOfType(DatasourceProvider.class)
				.values();
		for (DatasourceProvider dp : datasourceProviders) {
			datasourceProviderMap.put(dp.getName(), dp);
		}
		new Splash().doPrint();
	}
}
